<!DOCTYPE html>
<html lang=zh-CN>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="google-site-verification" content="xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta property="og:description" content="samuel-24276的博客">
    <meta property="og:type" content="website">
    <meta name="description" content="samuel-24276的博客">
    <meta name="keyword"  content="aircloud">
    <link rel="shortcut icon" href="/myblog/img/favicon.ico">

    <title>
        
        Effective C++读书笔记（2） - samuel-24276的博客 | samuel-24276&#39;s Blog
        
    </title>

    <!-- Custom CSS -->
    
<link rel="stylesheet" href="/myblog/css/aircloud.css">

    
<link rel="stylesheet" href="/myblog/css/gitment.css">

    <!--<link rel="stylesheet" href="https://imsun.github.io/gitment/style/default.css">-->
    <link href="//at.alicdn.com/t/font_620856_28hi1hpxx24.css" rel="stylesheet" type="text/css">
    <!-- ga & ba script hoook -->
    <script></script>
<meta name="generator" content="Hexo 7.1.1"></head>

<body>

<div class="site-nav-toggle" id="site-nav-toggle">
    <button>
        <span class="btn-bar"></span>
        <span class="btn-bar"></span>
        <span class="btn-bar"></span>
    </button>
</div>

<div class="index-about">
    <i> Like what you do, do what you like </i>
</div>

<div class="index-container">
    
    <div class="index-left">
        
<div class="nav" id="nav">
    <div class="avatar-name">
        <div class="avatar ">
            <img src="/myblog/img/avatar.jpg" />
        </div>
        <div class="name">
            <i>samuel-24276</i>
        </div>
    </div>
    <div class="contents" id="nav-content">
        <ul>
            <li >
                <a href="/myblog/">
                    <i class="iconfont icon-shouye1"></i>
                    <span>主页</span>
                </a>
            </li>
            <li >
                <a href="/myblog/tags">
                    <i class="iconfont icon-biaoqian1"></i>
                    <span>标签</span>
                </a>
            </li>
            <li >
                <a href="/myblog/archives">
                    <i class="iconfont icon-guidang2"></i>
                    <span>存档</span>
                </a>
            </li>
            <li >
                <a href="/myblog/collect/">
                    <i class="iconfont icon-shoucang1"></i>
                    <span>收藏</span>
                </a>
            </li>
            <li >
                <a href="/myblog/about/">
                    <i class="iconfont icon-guanyu2"></i>
                    <span>关于</span>
                </a>
            </li>
            
            <li>
                <a id="search">
                    <i class="iconfont icon-sousuo1"></i>
                    <span>搜索</span>
                </a>
            </li>
            
        </ul>
    </div>
    
        <div id="toc" class="toc-article">
    <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#2-%E6%9E%84%E9%80%A0-%E6%9E%90%E6%9E%84-%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97"><span class="toc-text">2.构造&#x2F;析构&#x2F;赋值运算</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE05%EF%BC%9A%E4%BA%86%E8%A7%A3C-%E9%BB%98%E9%BB%98%E7%BC%96%E5%86%99%E5%B9%B6%E8%B0%83%E7%94%A8%E5%93%AA%E4%BA%9B%E5%87%BD%E6%95%B0"><span class="toc-text">条款05：了解C++默默编写并调用哪些函数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE06%EF%BC%9A%E8%8B%A5%E4%B8%8D%E6%83%B3%E4%BD%BF%E7%94%A8%E7%BC%96%E8%AF%91%E5%99%A8%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E7%9A%84%E5%87%BD%E6%95%B0%EF%BC%8C%E5%B0%B1%E8%AF%A5%E6%98%8E%E7%A1%AE%E6%8B%92%E7%BB%9D"><span class="toc-text">条款06：若不想使用编译器自动生成的函数，就该明确拒绝</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE07%EF%BC%9A%E4%B8%BA%E5%A4%9A%E6%80%81%E5%9F%BA%E7%B1%BB%E5%A3%B0%E6%98%8Evirtual%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0"><span class="toc-text">条款07：为多态基类声明virtual析构函数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE08%EF%BC%9A%E5%88%AB%E8%AE%A9%E5%BC%82%E5%B8%B8%E9%80%83%E7%A6%BB%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0"><span class="toc-text">条款08：别让异常逃离析构函数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE09%EF%BC%9A%E7%BB%9D%E4%B8%8D%E5%9C%A8%E6%9E%84%E9%80%A0%E5%92%8C%E6%9E%90%E6%9E%84%E8%BF%87%E7%A8%8B%E4%B8%AD%E8%B0%83%E7%94%A8virtual%E5%87%BD%E6%95%B0"><span class="toc-text">条款09：绝不在构造和析构过程中调用virtual函数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE10%EF%BC%9A%E4%BB%A4operator-%E8%BF%94%E5%9B%9E%E4%B8%80%E4%B8%AAreference-to-this"><span class="toc-text">条款10：令operator&#x3D;返回一个reference to *this</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE11%EF%BC%9A%E5%9C%A8operator-%E4%B8%AD%E5%A4%84%E7%90%86%E2%80%9C%E8%87%AA%E6%88%91%E8%B5%8B%E5%80%BC%E2%80%9D"><span class="toc-text">条款11：在operator&#x3D;中处理“自我赋值”</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE12%EF%BC%9A%E5%A4%8D%E5%88%B6%E5%AF%B9%E8%B1%A1%E6%97%B6%E5%8B%BF%E5%BF%98%E5%85%B6%E6%AF%8F%E4%B8%80%E4%B8%AA%E6%88%90%E5%88%86"><span class="toc-text">条款12：复制对象时勿忘其每一个成分</span></a></li></ol></li></ol>
</div>
    
</div>


<div class="search-field" id="search-field">
    <div class="search-bg" id="search-bg"></div>
    <div class="search-container">
        <div class="search-input">
            <span id="esc-search"> <i class="icon-fanhui iconfont"></i></span>
            <input id="search-input"/>
            <span id="begin-search">搜索</span>
        </div>
        <div class="search-result-container" id="search-result-container">

        </div>
    </div>
</div>

        <div class="index-about-mobile">
            <i> Like what you do, do what you like </i>
        </div>
    </div>
    
    <div class="index-middle">
        <!-- Main Content -->
        


<div class="post-container">
    <div class="post-title">
        Effective C++读书笔记（2）
    </div>

    <div class="post-meta">
        <span class="attr">发布于：<span>2023-01-01 08:24:21</span></span>
        
        <span class="attr">标签：/
        
        <a class="tag" href="/myblog/tags/#C++" title="C++">C++</a>
        <span>/</span>
        
        
        </span>
        <span class="attr">访问：<span id="busuanzi_value_page_pv"></span>
</span>
</span>
    </div>
    <div class="post-content no-indent">
        <h1 id="2-构造-析构-赋值运算"><a href="#2-构造-析构-赋值运算" class="headerlink" title="2.构造&#x2F;析构&#x2F;赋值运算"></a>2.构造&#x2F;析构&#x2F;赋值运算</h1><h2 id="条款05：了解C-默默编写并调用哪些函数"><a href="#条款05：了解C-默默编写并调用哪些函数" class="headerlink" title="条款05：了解C++默默编写并调用哪些函数"></a>条款05：了解C++默默编写并调用哪些函数</h2><blockquote>
<p>Know what functions C++ silently writes and calls.</p>
</blockquote>
<p>编译器会为空类编写函数，<strong>default构造函数和析构函数</strong>主要是给编译器一个地方用来放置“藏身幕后”的 代码，像是调用base classes和non-static成员变量的构造函数和析构函数。注意，<em>编译器产出的析构函数是个non-virtual（见条款7），除非这个class的base class自身声明有virtual析构函数（这种情况下这个函数的虚属性主要来自base class）</em>。</p>
<p>编译器暗自创建的<strong>copy构造和copy assignment操作符（operator&#x3D;(类名&amp;)）</strong>，只是将来源对象的每一个non-static成员变量拷贝到目标对象。一般而言，只有产生的代码合法且有适当机会证明它有意义，编译器才能生成copy assignment操作符。</p>
<p>举个例子，假设NamedObject定义如下，其中nameValue是个reference to string，objectValue是个const T：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NamedObject</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">NamedObject</span>(std::string&amp; name, <span class="type">const</span> T&amp; value);</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">	std::string&amp; nameValue;</span><br><span class="line">    <span class="type">const</span> T objectValue;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>现在考虑下面会发生什么事：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::string <span class="title">newDog</span><span class="params">(<span class="string">&quot;suxi&quot;</span>)</span></span>;</span><br><span class="line"><span class="function">std::string <span class="title">oldDog</span><span class="params">(<span class="string">&quot;wangcai&quot;</span>)</span></span>;</span><br><span class="line"><span class="function">NamedObject&lt;<span class="type">int</span>&gt; <span class="title">p</span><span class="params">(newDog, <span class="number">5</span>)</span></span>;</span><br><span class="line"><span class="function">NamedObject&lt;<span class="type">int</span>&gt; <span class="title">s</span><span class="params">(oldDog, <span class="number">16</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">p = s;<span class="comment">// 现在p的成员变量该发生什么事？</span></span><br></pre></td></tr></table></figure>

<p>赋值之前，p.nameValue和s.nameValue都指向一个string对象（不是同一个）。C++不允许让引用指向不同对象，那么赋值时nameValue该如何修改呢？</p>
<p>面对这个难题，C++的响应是拒绝编译那一行赋值动作。**如果你打算在一个“内含reference成员”的class内支持赋值操作，你必须自己定义operator&#x3D;(类名&amp;)**。面对“内含const成员”的classes，编译器的反应也一样（更改const成员不合法）。最后还有一种情况：如果某个基类将operator&#x3D;声明为private，编译器将拒绝为其子类生成operator&#x3D;。</p>
<blockquote>
<p>编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符，以及析构函数。</p>
</blockquote>
<h2 id="条款06：若不想使用编译器自动生成的函数，就该明确拒绝"><a href="#条款06：若不想使用编译器自动生成的函数，就该明确拒绝" class="headerlink" title="条款06：若不想使用编译器自动生成的函数，就该明确拒绝"></a>条款06：若不想使用编译器自动生成的函数，就该明确拒绝</h2><blockquote>
<p>Explicitly disallow the use of compiler-generated functions you do not want.</p>
</blockquote>
<p>通常如果你不希望class支持某一特定机能，只要不声明对应函数就是了。但这个策略对copy构造函数和copy赋值函数(operator&#x3D;(类名&amp;))不起作用，见条款5。</p>
<p>关键在于，所有编译器产出的函数都是public。<em>为阻止这些函数被创建，你可以自行将其声明为private</em>。借由明确声明一个成员函数，你阻止了编译器暗自创建其专属版本；而令这些函数为private，使你得以成功阻止人们调用它。</p>
<p>一般而言这个做法并不绝对安全，因为member函数和friend函数还是可以调用你的private函数。除非你足够聪明，不去定义它们，那么如果某些人不慎调用任何一个，会获得一个链接错误。<strong>“将成员函数声明为private而且故意不去实现它们”这一伎俩是如此为大家接受，因而被用在C++ iostream程序库中阻止copying行为</strong>。是的，看看你手上的标准库实现代码中的ios_base,basic_ios和sentry。你会发现无论哪一个，其copy构造函数和copy assignment操作符都被声明为private而且没有定义。</p>
<p>示例代码如下：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">HomeForSale</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="built_in">HomeForSale</span>(<span class="type">const</span> HomeForSale&amp;);<span class="comment">// 只有声明</span></span><br><span class="line">    HomeForSale&amp; <span class="keyword">operator</span>=(<span class="type">const</span> HomeForSale&amp;);<span class="comment">// 只有声明</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>有了上述class定义，当客户企图拷贝HomeForSale对象，编译器会阻挠他。如果你不慎在member函数或friend函数之内那么做，轮到链接器发出抱怨。</p>
<p>将链接期错误移至编译期是可能的（越早侦测出错误越好），只要将copy构造函数和copy assignment操作符声明为private就可以办到，但不是在HomeForSale自身，而是在一个专门为了阻止copying动作而设计的base class内。这个base class非常简单：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Uncopyable</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">protected</span>:<span class="comment">// 允许子类对象构造和析构</span></span><br><span class="line">    <span class="built_in">Uncopyable</span>()&#123;&#125;</span><br><span class="line">    ~<span class="built_in">Uncopyable</span>()&#123;&#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="built_in">Uncopyable</span>(<span class="type">const</span> Uncopyable&amp;);<span class="comment">// 只有声明，阻止copying</span></span><br><span class="line">    Uncopyable&amp; <span class="keyword">operator</span>=(<span class="type">const</span> Uncopyable&amp;);<span class="comment">// 只有声明，阻止copying</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>为阻止HomeForSale对象被拷贝，我们唯一需要做的就是继承Uncopyable：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">HomeForSale</span>: <span class="keyword">private</span> Uncopyable</span><br><span class="line">&#123;</span><br><span class="line">  ...  </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>这行得通，正如条款12所说，这些函数（default构造、析构、拷贝构造、拷贝赋值）的“编译器生成版”会尝试调用其基类的对应函数，那些调用会被编译器拒绝，因为其基类的拷贝函数是private。</p>
<p>Uncopyable类的实现和运用颇为微妙，包括不一定要以public继承它（见条款32和39），以及Uncopyable的析构函数不一定要是virtual（见条款7）等等。Uncopyable不含数据，因此符合条款39所描述的empty base class optimization资格。但由于它总是扮演基类，因此使用这项技术可能导致多重继承（多重继承见条款40），儿多重继承有时会阻止empty base class optimization。通常你可以忽略这些微妙点，只像上面那样使用Uncopyable，因为它完全像“广告”所说的能够正确运作。也可以使用Boost（见条款55）提供的版本，那个class名为noncopyable，是个还不错的家伙，我只是认为其名称有点不太自然。</p>
<blockquote>
<p>为驳回编译器自动（暗自）提供的机能，可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。</p>
</blockquote>
<h2 id="条款07：为多态基类声明virtual析构函数"><a href="#条款07：为多态基类声明virtual析构函数" class="headerlink" title="条款07：为多态基类声明virtual析构函数"></a>条款07：为多态基类声明virtual析构函数</h2><blockquote>
<p>Declare destructors virtual in polymorphic base classes.</p>
</blockquote>
<p>有许多种做法可以记录时间，因此，设计一个TimeKeeper基类和一些派生类作为不同的计时方法，相当合情合理：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TimerKeeper</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">TimeKeeper</span>();</span><br><span class="line">    ~<span class="built_in">TimeKeeper</span>();</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AtomicClock</span>: <span class="keyword">public</span> TimeKeeper &#123; ... &#125;;<span class="comment">// 原子钟</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WaterClock</span>: <span class="keyword">public</span> TimeKeeper &#123; ... &#125;;<span class="comment">// 水钟</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WristWatch</span>: <span class="keyword">public</span> TimeKeeper &#123; ... &#125;;<span class="comment">// 腕表</span></span><br></pre></td></tr></table></figure>

<p>我们可以设计factory函数，返回指针指向一个计时对象：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">TimeKeper* <span class="title">getTimeKeeper</span><span class="params">()</span></span>;<span class="comment">// 返回一个指针，指向一个TimeKeeper派生类的动态分配对象</span></span><br></pre></td></tr></table></figure>

<p>为遵守factory函数的规矩，被getTimeKeeper()返回的对象必须位于heap。因此为了避免泄露内存和其他资源，将factory函数返回的每一个对象适当地delete掉很重要：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">TimeKeeper* ptk = <span class="built_in">getTimeKeeper</span>();<span class="comment">// 从TimeKeeper继承体系获得一个动态分配对象</span></span><br><span class="line">...<span class="comment">// 运用它</span></span><br><span class="line"><span class="keyword">delete</span> ptk;<span class="comment">// 释放它，避免资源泄露</span></span><br></pre></td></tr></table></figure>

<p>条款13说“依赖客户执行delete动作，基本上便带有某种错误倾向”，条款18则谈到factory函数接口该如何修改以便预防常见之客户错误。但这些在此都是次要的，因为此条款内我们要对付的是上述代码的一个更根本弱点：纵使客户把每一件事都做对了，仍然没办法知道程序如何行动。</p>
<p>问题出在getTimeKeeper返回的指针指向一个子类对象，而那个子类对象确经由一个基类指针被删除，而目前的基类（TimeKeeper）有个non-virtual析构函数。该删除实际执行时通常发生的是对象的derived成分没被销毁，造成一个诡异的“局部销毁”对象。这可是形成资源泄露、败坏数据结构、在调试器上浪费许多时间的绝佳途径喔。</p>
<p>消除这个问题的方法很简单：给基类一个virtual析构函数。像TimeKeeper这样的基类除了析构函数之外通常还有其他virtual函数，因为virtual函数的目的是允许derived class的实现得以客制化（见条款34）。</p>
<p>如果class不含virtual函数，通常表示它并不意图被用作一个基类。当class不企图被当作base class，令其析构函数为virtual往往是个馊主意。考虑一个用来表示二维空间点坐标的class：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Point</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Point</span>(<span class="type">int</span> xCoord, <span class="type">int</span> yCoord);</span><br><span class="line">    ~<span class="built_in">Point</span>();</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">int</span> x, y;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>如果int占32bits，那么Point对象可塞入一个64-bit缓存器中。更有甚者，这样一个Point对象可被当做一个“64-bit量”传给其他语言如C或FORTRAN撰写的函数。然而当Point的析构函数是virtual，形势起了变化。</p>
<p><strong>欲实现出virtual函数，对象必须携带某些信息，主要用来在运行期决定哪一个virtual函数该被调用。</strong>这份信息通常是由一个所谓vptr（virtual table pointer）指针指出。vptr指向一个由函数指针构成的数组，称为vtbl（virtual table）；每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数，实际被调用的函数取决于该对象vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。</p>
<p>如果Point类内含有virtual函数，其对象的体积会增加，在32-bit计算机体系结构中将占用64bits（为了存放两个ints）至96bits（两个int加上vptr）；在64-bits计算机体系结构中可能占用64-128bits，因为指针在这样的计算机结构中占64bits。因此，为Point添加一个vptr会增加其对象大小达50%-100%！Point对象不再能够塞入一个64-bit缓存器，而C++的Point对象也不再和其他语言（如C）内的相同声明有着一样的结构（因为其它语言的对应物并没有vptr），因此也就不再可能把它传递至（或接受自）其他语言所写的函数，除非你明确补偿vptr——那属于实现细节，也因此不再具有移植性。</p>
<p>许多人的心得是：只有当class内至少含有一个virtual函数，才为它声明virtual析构函数。</p>
<blockquote>
<ol>
<li>polymorphic（带多态性质的）base classes应该声明一个virtual析构函数。如果class带有任何virtual函数，它就应该拥有一个virtual析构函数。</li>
<li>Classes的设计目的如果不是作为base classes使用，或不是为了具备多态性（polymorphically），就不该声明virtual析构函数。</li>
</ol>
</blockquote>
<h2 id="条款08：别让异常逃离析构函数"><a href="#条款08：别让异常逃离析构函数" class="headerlink" title="条款08：别让异常逃离析构函数"></a>条款08：别让异常逃离析构函数</h2><blockquote>
<p>Prevent exceptions from leaving destructors.</p>
</blockquote>
<p>假设在析构函数内需要析构一个std::vector<Widget>，而在析构第一个元素期间，有个异常被抛出，其他的Widget还是应该被销毁（否则它们保存的任何资源都会发生泄漏），因此v应该调用它们各个的析构函数。但假设在那些调用期间，第二个Widget析构函数又抛出异常。现在有两个不同的异常，在两个异常同时存在的情况下，程序若不是结束执行就是导致不明确行为。</p>
<p>这很容易理解，但如果你的析构函数必须执行一个动作，而该动作可能会在失败时抛出异常，该怎么办？举个例子，假设你使用一个class负责数据库连接：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DBConnection</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="type">static</span> DBConnection <span class="title">create</span><span class="params">()</span></span>;<span class="comment">// 返回DBConnection对象，为求简化暂略参数</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">close</span><span class="params">()</span></span>;<span class="comment">// 关闭联机，失败则抛出异常</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为确保客户不忘记在DBConnection对象身上调用close()，一个合理的想法是创建一个用来管理DBConnection资源的class，并在其析构函数中调用close()。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DBConn</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    ~<span class="built_in">DBConn</span>()<span class="comment">// 确保数据库连接总是被关闭</span></span><br><span class="line">    &#123;</span><br><span class="line">        db.<span class="built_in">close</span>();</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    DBConnection db;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这允许客户写出这样的代码：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="function">DBConn <span class="title">dbc</span><span class="params">(DBConnection::create())</span></span>;<span class="comment">//区块结束时，DBConn对象被销毁，因而自动为DBConnection对象调用close</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>只要close成功，一切都美好。但如果该调用导致异常，DBConn析构函数会传播该异常，也就是允许它离开这个析构函数。那就会造成问题，因为那就是抛出了难以驾驭的麻烦。</p>
<p>两个办法可以避免这一问题。DBConn的析构函数可以：</p>
<ul>
<li><p>如果close抛出异常就结束程序。通常通过调用abort完成：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">DBConn::~<span class="built_in">DBConn</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123; db.<span class="built_in">close</span>(); &#125;</span><br><span class="line">    <span class="built_in">catch</span> (...)&#123;</span><br><span class="line">    	<span class="comment">// 制作运转记录，记下对close的调用失败</span></span><br><span class="line">        std::<span class="built_in">abort</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果程序遭遇一个“于析构期间发生的错误”后无法继续执行，“强迫结束程序”是个合理选项。毕竟它可以阻止异常从析构函数传播出去（那会导致不明确的行为）。也就是说调用abort可以抢先制“不明确行为”于死地。</p>
</li>
<li><p>吞下因调用close而发生的异常：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">DBConn::~<span class="built_in">DBConn</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123; db.<span class="built_in">close</span>(); &#125;</span><br><span class="line">    <span class="built_in">catch</span> (...)&#123;</span><br><span class="line">    	<span class="comment">// 制作运转记录，记下对close的调用失败</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>一般而言，将异常吞掉是个坏主意，因为它压制了“某些动作失败”的重要信息！然而有时候吞下异常也比负担“草率结束程序”或“不明确行为带来的风险”好。</p>
</li>
</ul>
<p>这些方法都没什么吸引力。问题在于两者都无法对“导致close抛出异常”的情况做反应。</p>
<p>一个较好的策略是重新设计DBConn接口，使其客户有机会对可能出现的问题作出反应。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DBConn</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">close</span><span class="params">()</span><span class="comment">// 供客户使用的新函数</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        db.<span class="built_in">close</span>();</span><br><span class="line">        closed = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    ~<span class="built_in">DBConn</span>()</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span>(!closed)&#123;</span><br><span class="line">            <span class="keyword">try</span>&#123;<span class="comment">// 关闭连接（如果客户不那么做的话）</span></span><br><span class="line">                db.<span class="built_in">close</span>();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">catch</span> (...) &#123;</span><br><span class="line">                <span class="comment">// 制作运转记录，记下对close的调用失败</span></span><br><span class="line">                ...<span class="comment">// 记录下来并结束程序或吞下异常</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    DBConnection db;</span><br><span class="line">    <span class="type">bool</span> closed;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>DBConn自己可以提供一个close函数，因而赋予客户一个机会得以处理“因该操作而发生的异常”。DBConn也可以追踪其所管理之DBConnection是否已被关闭，并在答案为否的情况下由其析构函数关闭之。这可防止遗失数据库连接。然而如果DBConnection构造函数调用close失败，我们又将退回“强迫结束程序”或“吞下异常”的老路。</p>
<p>把调用close的责任从DBConn析构函数手上移到DBConn客户手上（但DBConn析构函数仍内含一个“双保险”调用）可能会给你“肆无忌惮转移负担”的印象。本例要说的是，由客户自己调用close并不会对他们带来负担，而是给他们一个处理错误的机会，否则他们没机会响应。如果他们认为这个机会没用（或许他们坚信不会有错误发生），可以忽略它，依赖DBConn析构函数去调用close。如果真有错误发生——如果close的确抛出异常——而且DBConn吞下该异常或结束程序，客户没有立场抱怨，毕竟它们曾有机会第一手处理问题，而他们选择了放弃。</p>
<blockquote>
<ol>
<li>析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常，析构函数应该捕获任何异常，然后吞下它们（不传播）或结束程序。</li>
<li>如果客户需要对某个操作函数运行期间抛出的异常做出反应，那么class应该提供一个普通函数（而非在析构函数中）执行该操作。</li>
</ol>
</blockquote>
<h2 id="条款09：绝不在构造和析构过程中调用virtual函数"><a href="#条款09：绝不在构造和析构过程中调用virtual函数" class="headerlink" title="条款09：绝不在构造和析构过程中调用virtual函数"></a>条款09：绝不在构造和析构过程中调用virtual函数</h2><blockquote>
<p>Never call virtual functions during construction or destruction.</p>
</blockquote>
<p>假设你有个class继承体系，用来塑模股市交易如买进、卖出的订单等等。这样的交易一定要经过审计，所以每当创建一个交易对象，在审计日志（audit log）中页需要创建一笔适当记录。下面是一个看起来颇为合理的做法：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Transaction</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Transaction</span>();</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">logTransaction</span><span class="params">()</span> <span class="type">const</span> </span>= <span class="number">0</span>;<span class="comment">// 做出一份因类型不同而不同的日志记录（log entry）</span></span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Transaction::<span class="built_in">Transaction</span>()</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="built_in">logTransaction</span>();<span class="comment">// 最后动作是志记这笔交易</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BuyTransaction</span>: <span class="keyword">public</span> Transaction&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">logTransaction</span><span class="params">()</span> <span class="type">const</span></span>;<span class="comment">// 志记此型交易</span></span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SellTransaction</span>: <span class="keyword">public</span> Transaction&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">logTransaction</span><span class="params">()</span> <span class="type">const</span></span>;<span class="comment">// 志记此型交易</span></span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>现在，执行以下操作会发生什么事：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">BuyTransaction b;</span><br></pre></td></tr></table></figure>

<p>无疑地会有BuyTransaction地构造函数被调用，但首先Transaction构造函数一定会被更早调用：是的，<strong>子类对象内的基类成分会在子类自身成分被构造前先构造妥当</strong>。Transaction构造函数地最后一行调用虚函数logTransaction，这正是引发惊奇地起点。这时候被调用的logTransaction是Transaction内的版本，不是BuyTransaction内的版本——即使目前即将建立的对象类型是BuyTransaction。<strong>base class构造期间virtual函数绝不会下降到derived classes阶层。非正式地说法或许比较传神：在base class构造期间，virtual函数不是virtual函数</strong>。</p>
<p>如果base class构造期间virtual函数下降到derived classes阶层，此时derived class地函数几乎必然取用local成员变量，而那些成员变量尚未初始化。其实还有比上述理由更根本地原因：<strong>在derived class对象的base class构造期间，对象的类型是base class而不是derived class。不止virtual函数会被编译器解析至base class，若使用运行期类型信息（runtime type information，例如dynamic_cast（见条款27）和typeid），也会把对象视为base class类型。对象在derived class构造函数开始执行前不会成为一个derived class对象。</strong></p>
<p>相同道理也适用于析构函数。一旦derived class析构函数开始执行，对象内的derived class成员变量便呈现未定义值。进入base class析构函数后对象就成为一个base class对象，而C++的任何部分包括virtual函数、dynamic_cast等等也都那么看待它。</p>
<p>在上述示例中，构造函数内直接调用一个virtual函数，由于很容易被看出来，某些编译器会为此发出一个警告信息（某些则否，见条款53对警告信息的讨论）。但是侦测“构造函数或析构函数运行期间是否调用virtual函数”并不总是如此轻松。如果Transation有多个构造函数，每个都要执行某些相同工作，那么避免代码重复的一个优秀做法是把共同的初始化代码（其中包含对logTransaction的调用）放进一个初始化函数如init内：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Transaction</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Transaction</span>()</span><br><span class="line">    &#123; <span class="built_in">init</span>(); &#125;<span class="comment">// 调用non-virtual</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">logTransaction</span><span class="params">()</span> <span class="type">const</span> </span>= <span class="number">0</span>;</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="built_in">logTransaction</span>();<span class="comment">// 调用virtual</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>这段代码容易暗中为害，因为它通常不会引发任何编译器和链接器的抱怨。此时因为logTransaction是Transaction的pure virtual函数，调用纯虚函数时，大多数执行系统会终止程序（通常会对此结果发出一个信息，编译不会报错的）。然而如果logTransaction是个正常的（有实现，非纯虚）virtual函数，该版本就会被调用，留下你百思不得其解为什么建立子类对象却调用了父类的虚函数。唯一能避免此问题的做法就是：<strong>确定你的构造和析构函数都没有（在对象创建和被销毁期间）调用virtual函数，而它们调用的所有函数也都服从同一约束</strong>。</p>
<p>那么如何确保每次创建Transaction子类对象时，可以调用适当版本的logTransaction呢？显然，在Transaction构造函数内调用virtual虚函数是一种错误做法。</p>
<p>一种正确的做法是在Transaction类内将logTransaction函数改为non-virtual，然后要求子类构造函数传递必要信息给Transaction构造函数，而后那个构造函数便可以安全地调用非虚函数logTransaction。像这样：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Transaction</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">Transaction</span><span class="params">(<span class="type">const</span> std::string &amp;logInfo)</span></span>;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">logTransaction</span><span class="params">(<span class="type">const</span> std::string &amp;logInfo)</span> <span class="type">const</span></span>; </span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Transaction::<span class="built_in">Transaction</span>(<span class="type">const</span> std::string &amp;logInfo)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="built_in">logTransaction</span>(logInfo);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BuyTransaction</span>: <span class="keyword">public</span> Transaction</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">BuyTransaction</span>( parameters )</span><br><span class="line">        : <span class="built_in">Transaction</span>( <span class="built_in">createLogString</span>(parameters) )<span class="comment">// 将log信息传递给base class构造函数</span></span><br><span class="line">        &#123;...&#125;</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">createLogString</span><span class="params">(parameters)</span></span>; </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p><strong>由于你无法使用virtual函数从base class向下调用，在构造期间，你可以藉由“令derived class将必要的构造信息向上传递至base class构造函数”替换之加以弥补</strong>。请注意本例中BuyTransaction内的private static函数createLogString的运用，比起在成员初值列内给予base class所需数据，利用辅助函数创建一个值传给base class构造函数往往更方便（也比较可读）。令此函数为static，也就不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。</p>
<blockquote>
<p>在构造和析构期间不要调用virtual函数，因为这类调用从不下降至derived class（比起当前执行构造函数和析构函数的那层）。</p>
</blockquote>
<h2 id="条款10：令operator-返回一个reference-to-this"><a href="#条款10：令operator-返回一个reference-to-this" class="headerlink" title="条款10：令operator&#x3D;返回一个reference to *this"></a>条款10：令operator&#x3D;返回一个reference to *this</h2><blockquote>
<p>Having assignment operators return a reference to *this.</p>
</blockquote>
<p>为实现“连锁赋值”，赋值操作符必须返回一个reference指向操作符的左侧实参。</p>
<blockquote>
<p>令赋值（assignment）操作符返回一个reference to *this。</p>
</blockquote>
<h2 id="条款11：在operator-中处理“自我赋值”"><a href="#条款11：在operator-中处理“自我赋值”" class="headerlink" title="条款11：在operator&#x3D;中处理“自我赋值”"></a>条款11：在operator&#x3D;中处理“自我赋值”</h2><blockquote>
<p>Handle assignment to self in operator&#x3D;.</p>
</blockquote>
<p>“别名”就是“有一个以上的方法指称（指涉）某对象”。别名会导致并不明显的自我赋值：例如：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">a[i] = a[j];<span class="comment">// 潜在的自我赋值</span></span><br><span class="line">*px = *py;<span class="comment">// 潜在的自我赋值</span></span><br></pre></td></tr></table></figure>

<p><strong>一般而言如果某段代码操作pointers或references，而它们被用来“指向多个相同类型的对象”，就需考虑这些对象是否为同一个。实际上两个对象只要来自同一个继承体系，它们甚至不需声明为相同类型就可能造成“别名”，因为一个base class的reference或pointer可以指向一个derived class对象。</strong></p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Base</span> &#123; ... &#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Derived</span>: <span class="keyword">public</span> Base &#123; ... &#125;;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">doSomething</span><span class="params">(<span class="type">const</span> Base &amp;rb, Derived &amp;pd)</span></span>;<span class="comment">// rb和pd有可能其实是同一对象</span></span><br></pre></td></tr></table></figure>

<p>如果遵循条款13和14的忠告，你会运用对象来管理资源，而且你可以确定所谓“资源管理对象”在copy发生时有正确的举措。这种情况下你的赋值操作符或许是“自我赋值安全的”，不需要额外操心。然而如果你尝试自行管理资源，可能会掉进“在停驶使用资源之前意外释放了它的”陷阱。假设你建立一个class用来保存一个指针指向一块动态分配的位图：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Bitmap</span> &#123; ... &#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span>&#123;</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    Bitmap* pb;<span class="comment">// 指针，指向一个从heap分配而得的对象</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Widget&amp; Widget::<span class="keyword">operator</span>=(<span class="type">const</span> Widget&amp; rhs)<span class="comment">// 一份不安全的operator=版本</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">delete</span> pb;<span class="comment">// 停止使用当前的bitmap</span></span><br><span class="line">    pb = <span class="keyword">new</span> <span class="built_in">Bitmap</span>(*rhs.pb);<span class="comment">// 使用rhs的副本</span></span><br><span class="line">    <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>若上述赋值操作符内的*this和rhs是一个对象，则其返回的指针指向一个被删除的对象！欲阻止这种错误，传统做法是藉由operator&#x3D;最前面的一个“证同测试”达到“自我赋值”的检验目的：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Widget&amp; Widget::<span class="keyword">operator</span>=(<span class="type">const</span> Widget&amp; rhs)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">this</span> == &amp;rhs) <span class="keyword">return</span> *<span class="keyword">this</span>;<span class="comment">// 证同测试，如果是自我赋值，就不做任何事</span></span><br><span class="line">    <span class="keyword">delete</span> pb;</span><br><span class="line">    pb = <span class="keyword">new</span> <span class="built_in">Bitmap</span>(*rhs.pb);</span><br><span class="line">    <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码具备了“自我赋值安全性”，但不具备“异常安全性”，可参见条款29对异常安全性的讨论。</p>
<blockquote>
<ol>
<li>确保当对象自我赋值时operator&#x3D;有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。</li>
<li>确定任何函数如果操作一个以上的对象，而其中多个对象是同一个对象时，其行为仍然正确。</li>
</ol>
</blockquote>
<h2 id="条款12：复制对象时勿忘其每一个成分"><a href="#条款12：复制对象时勿忘其每一个成分" class="headerlink" title="条款12：复制对象时勿忘其每一个成分"></a>条款12：复制对象时勿忘其每一个成分</h2><blockquote>
<p>Copy all parts of an object.</p>
</blockquote>
<p>如果你为class添加一个成员变量，你必须同时修改copying函数（**你也需要修改class的所有构造函数（见条款4和条款45）以及任何非标准形式的operator&#x3D;**）。任何时候只要你承担起“为derived class撰写copying函数”的重责大任，必须很小心地复制其base class成分。那些成分往往是private（见条款22），你应该让derived class的copying函数调用相应的base class函数。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">PriorityCustomer::<span class="built_in">PriorityCustomer</span>(<span class="type">const</span> PriorityCustomer &amp;rhs)</span><br><span class="line">    : <span class="built_in">Customer</span>(rhs)</span><br><span class="line">      <span class="built_in">priority</span>(rhs.priority)</span><br><span class="line">&#123;</span><br><span class="line">	<span class="built_in">logCall</span>(<span class="string">&quot;PriorityCustomer copy constructor&quot;</span>);      </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">PriorityCustomer&amp; PriorityCustomer::<span class="keyword">operator</span>=(<span class="type">const</span> PriorityCustomer&amp; rhs)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">logCall</span>(<span class="string">&quot;PriorityCustomer copy assignment operator&quot;</span>);</span><br><span class="line">    Customer::<span class="keyword">operator</span>=(rhs);<span class="comment">// 对base class成分进行赋值操作(不正确的语法)</span></span><br><span class="line">    priority = rhs.priority;</span><br><span class="line">    <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>令copy assignment操作符调用copy构造函数是不合理的，因为这就像试图构造一个已经存在的对象。</p>
<p><strong>如果你发现你的copy构造函数和copy assignment操作符有相近的代码，消除重复代码的做法是：建立一个新的成员函数给两者调用。</strong>这样的函数往往是private而且常被命名为init。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复、</p>
<blockquote>
<ol>
<li>Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。</li>
<li>不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中，并由两个copying函数共同调用。</li>
</ol>
</blockquote>
<hr>

        
            <div class="donate-container">
    <div class="donate-button">
        <button id="donate-button">赞赏</button>
    </div>
    <div class="donate-img-container hide" id="donate-img-container">
        <img id="donate-img" src="" data-src="/myblog/img/donate.jpg">
        <p> 感谢鼓励 </p>
    </div>
</div>
        
        <br />
        <div id="comment-container">
        </div>
        <div id="disqus_thread"></div>
        <div id="lv-container"></div>
        <div class="giscus"></div>
    </div>
</div>

    </div>
</div>


<footer class="footer">
    <ul class="list-inline text-center">
        
        

        

        

        
        <li>
            <a target="_blank"  href="https://github.com/samuel-24276">
                            <span class="fa-stack fa-lg">
                                <i class="iconfont icon-github"></i>
                            </span>
            </a>
        </li>
        

        

    </ul>
    
    <p>
        <span>/</span>
        
        <span><a target="_blank" rel="noopener" href="https://github.com/samuel-24276">samuel-24276&#39;s Page</a></span>
        <span>/</span>
        
        <span><a target="_blank" rel="noopener" href="https://beian.miit.gov.cn/">北京-未备案</a></span>
        <span>/</span>
        
        <span><a href="#">返回顶部</a></span>
        <span>/</span>
        
    </p>
    
    <p>
        <span id="busuanzi_container_site_pv">
            <span id="busuanzi_value_site_pv"></span>PV
        </span>
        <span id="busuanzi_container_site_uv">
            <span id="busuanzi_value_site_uv"></span>UV
        </span>
        Created By <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a>  Theme <a target="_blank" rel="noopener" href="https://github.com/aircloud/hexo-theme-aircloud">AirCloud</a></p>
</footer>




</body>

<script>
    // We expose some of the variables needed by the front end
    window.hexo_search_path = "search.json"
    window.hexo_root = "/myblog/"
    window.isPost = true
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

<script src="/myblog/js/index.js"></script>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>






    <script type="text/javascript">
       (function() {
           if (typeof LivereTower === 'function') { return; }

           var j, d = document.getElementById('lv-container');

           d.setAttribute('data-id','city');
           d.setAttribute('data-uid' , 'MTAyMC80OTA5OC8yNTU5Mw==');

           j = document.createElement('script');
           j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
           j.async = true;

           d.appendChild(j);
       })();
    </script>
    <noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
    </div>

</html>
